package com.leyou.search.service.impl;

import com.leyou.exception.LyException;

import com.leyou.item.client.ItemClient;
import com.leyou.search.constants.SearchConstants;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.SearchService;
import com.leyou.starter.elastic.entity.PageInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private GoodsRepository goodsRepository;

    @Override
    public Mono<List<String>> generateSuggest(String key) {

        if (StringUtils.isEmpty(key)){
            throw new LyException(400,"参数不能为空");
        }
        return  this.goodsRepository.suggestBySingleField(SearchConstants.SUGGESTION_FIELD, key);
    }
    @Override
    public Mono<PageInfo<Goods>> listData(SearchRequest searchRequest) {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //添加查询条件，查询属性为title，分词查询，
        sourceBuilder.query(getQueryBuilder(searchRequest));

        //分页条件
        int from = searchRequest.getPage() == null ? 0 : (searchRequest.getPage() - 1) * searchRequest.getSIZE();

        sourceBuilder.from(from);
        sourceBuilder.size(searchRequest.getSIZE());

        //高亮条件
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<am>");
        highlightBuilder.postTags("</am>");
        highlightBuilder.field("title");

        sourceBuilder.highlighter(highlightBuilder);

        Set<String> sortCondition = new HashSet<>();

        sortCondition.add("prices");
        sortCondition.add("sold");
        sortCondition.add("updateTime");

        String filed = searchRequest.getSortBy();

        //排序字段不为空做排序
        if (StringUtils.isNotEmpty(filed) && sortCondition.contains(filed)) {

            //添加排序条件，排序字段为sortBy，排序方式：当desc为true就是desc，否则asc
            sourceBuilder.sort(SortBuilders.fieldSort(filed).order(searchRequest.getDesc() ? SortOrder.DESC : SortOrder.ASC));
        }


        return goodsRepository.queryBySourceBuilderForPageHighlight(sourceBuilder);
    }

    //构造查询条件
    private QueryBuilder getQueryBuilder(SearchRequest searchRequest) {
        String key = searchRequest.getKey();
        if (StringUtils.isEmpty(key)) {
            throw new LyException(400, "查询参数不能为空");
        }

        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

        //给boolean添加查询条件filterEntry.getKey()
        boolQueryBuilder.must(QueryBuilders.matchQuery("title", key).operator(Operator.AND));

        //过滤条件是否添加，取决于filterMap中是否有值
        if (!CollectionUtils.isEmpty(searchRequest.getFilters())) {
            for (Map.Entry<String, String> filterEntry : searchRequest.getFilters().entrySet()) {
                //过滤条件的key值
                String queryKey = filterEntry.getKey();

                //过滤条件的value值
                String queryValue = filterEntry.getValue();

                //key替换为brandId或者categoryId
                if ("品牌".equals(queryKey)) {
                    boolQueryBuilder.filter(QueryBuilders.termQuery("brandId", queryValue));
                } else if ("分类".equals(queryKey)) {
                    boolQueryBuilder.filter(QueryBuilders.termQuery("categoryId", queryValue));
                } else {//其他参数，查询本身nested查询
                    BoolQueryBuilder subBoolean = QueryBuilders.boolQuery();
                    subBoolean.must(QueryBuilders.termQuery("specs.name", queryKey));
                    subBoolean.must(QueryBuilders.termQuery("specs.value", queryValue));
                    boolQueryBuilder.filter(QueryBuilders.nestedQuery("specs", subBoolean, ScoreMode.None));
                }
            }
        }

        return boolQueryBuilder;
    }

    @Override
    public Mono<Map<String, List<?>>> listFilter(SearchRequest searchRequest) {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //添加查询条件
        sourceBuilder.query(getQueryBuilder(searchRequest));

        //要查询但是不展示结果，
        sourceBuilder.size(0);

        //添加聚合条件

        sourceBuilder.aggregation(AggregationBuilders.terms("brandAgg").field("brandId").size(30));
        sourceBuilder.aggregation(AggregationBuilders.terms("categoryAgg").field("categoryId").size(30));


        //添加规格参数的nested聚合以及对应的子聚合
        sourceBuilder.aggregation(
                AggregationBuilders.nested("specsAgg", "specs")
                        .subAggregation(AggregationBuilders.terms("nameAgg").field("specs.name").size(30)
                                .subAggregation(AggregationBuilders.terms("valueAgg").field("specs.value").size(30)))

        );


        //异步聚合处理
        Mono<Aggregations> aggregationsMono = goodsRepository.aggregationBySourceBuilder(sourceBuilder);


        //mono中map，对每个元素进行挨个运算，对mono中的内容进行二次封装
        return aggregationsMono.map(aggregations -> {
            Map<String, List<?>> filterResult = new LinkedHashMap<>();


            //根据聚合名称获取聚合结果
            Terms categoryAgg = aggregations.get("categoryAgg");

            //从聚合结果中获取当前聚合key的所有值
            List<Long> categoryIds = categoryAgg
                    .getBuckets()
                    .stream()
                    .map(bucket -> ((Terms.Bucket) bucket).getKeyAsNumber())
                    .map(Number::longValue)
                    .collect(Collectors.toList());

            if (!CollectionUtils.isEmpty(categoryIds)) {
                filterResult.put("分类", this.itemClient.listCategoryByIds(categoryIds));
            }


            //ParsedLongTerms 根据聚合名称获取聚合结果对象
            Terms brandAgg = aggregations.get("brandAgg");


            List<Long> brandIds = brandAgg
                    .getBuckets()
                    .stream()
                    .map(bucket -> ((Terms.Bucket) bucket).getKeyAsNumber())
                    .map(Number::longValue)
                    .collect(Collectors.toList());

            if (!CollectionUtils.isEmpty(brandIds)) {
                filterResult.put("品牌", this.itemClient.listBrandByIds(brandIds));
            }

            //根据聚合名称获取nested聚合
            Nested specsAgg = aggregations.get("specsAgg");

            //根据聚合名称获取名称聚合
            Terms nameAgg = specsAgg.getAggregations().get("nameAgg");

            nameAgg.getBuckets().forEach(bucket -> {

                //获取key就是规格参数的名称
                String resultKey = ((Terms.Bucket) bucket).getKeyAsString();

                //继续需要根据聚合名称获取聚合结果
                Terms valueAgg = ((Terms.Bucket) bucket).getAggregations().get("valueAgg");

                //从value的聚合结果中获取结果集合
                List<String> valueList = valueAgg.getBuckets().stream().map(valueBucket -> {
                    return ((Terms.Bucket) valueBucket).getKeyAsString();
                }).collect(Collectors.toList());

                //把对应key和value集合存入result
                filterResult.put(resultKey, valueList);
            });


            return filterResult;
        });
    }
}
